/**
 * 签到码服务
 * 负责动态签到码的生成、验证、过期检查
 * 只有协管（MODERATOR）角色的成员才能生成签到码
 *
 * 存储方式：使用 Redis 进行持久化存储（支持分布式部署）
 */

import { generateNumericCode } from '@services/common/utils'
import { SECRET_CODE_EXPIRY_SECONDS } from '@services/common/constants'
import { PermissionError, NotFoundError } from '@services/common/errors'
import { GenerateSecretCodeDTO, SecretCodeDTO, ValidateSecretCodeDTO, ServiceResult } from '@services/common/types'
import { createSuccessResult, handleServiceError } from '@services/common/utils'
import { checkModeratorPermission } from '@services/auth.service'
import * as eventRepo from '@repos/event/event.repo'
import * as RedisService from '@services/redis.service'

/**
 * 生成签到码
 * @param data 签到码生成信息
 */
export async function generateSecretCode(data: GenerateSecretCodeDTO): Promise<ServiceResult<SecretCodeDTO>> {
    try {
        // 1. 验证事件是否存在
        const eventResult = await eventRepo.checkEventExists(data.eventId)
        if (!eventResult.status || !eventResult.data) {
            throw new NotFoundError('Event')
        }

        // 2. 检查生成者是否有协管权限
        // 需要从事件获取组织ID
        const eventDetailResult = await eventRepo.readEvent(data.eventId)
        if (!eventDetailResult.status || !eventDetailResult.data) {
            throw new NotFoundError('Event')
        }

        const organizationId = eventDetailResult.data.organizationId

        const permissionResult = await checkModeratorPermission(data.generatedBy, organizationId)
        if (!permissionResult.success || !permissionResult.data) {
            throw new PermissionError('Only moderators can generate check-in codes')
        }

        // 3. 生成6位数字签到码
        const code = generateNumericCode(6)

        // 4. 计算过期时间
        const expiresIn = data.expiresIn || SECRET_CODE_EXPIRY_SECONDS
        const expiresAt = new Date(Date.now() + expiresIn * 1000)

        // 5. 存储签到码到 Redis
        const codeData: RedisService.SecretCodeData = {
            code,
            eventId: data.eventId,
            generatedBy: data.generatedBy,
            expiresAt: expiresAt.toISOString()
        }

        await RedisService.setSecretCode(data.eventId, code, codeData, expiresIn)

        return createSuccessResult(
            {
                code,
                eventId: data.eventId,
                expiresAt,
                generatedBy: data.generatedBy
            },
            'Secret code generated successfully'
        )
    } catch (error) {
        return handleServiceError(error)
    }
}

/**
 * 验证签到码
 * @param data 签到码验证信息
 */
export async function validateSecretCode(data: ValidateSecretCodeDTO): Promise<ServiceResult<boolean>> {
    try {
        // 1. 从 Redis 获取签到码
        const stored = await RedisService.getSecretCode(data.eventId, data.code)

        if (!stored) {
            return createSuccessResult(false, 'No active code for this event')
        }

        // 2. 检查是否过期（Redis 会自动清除过期的键，但双重检查）
        if (new Date() > new Date(stored.expiresAt)) {
            await RedisService.deleteSecretCode(data.eventId, stored.code)
            return createSuccessResult(false, 'Code has expired')
        }

        // 3. 验证码匹配（已经通过 key 匹配，所以一定是有效的）
        return createSuccessResult(true, 'Code is valid')
    } catch (error) {
        return handleServiceError(error)
    }
}

/**
 * 获取当前有效的签到码（仅供管理员/协管查看）
 * @param eventId 事件ID
 * @param memberId 成员ID（用于权限验证）
 */
export async function getActiveSecretCode(
    eventId: string,
    memberId: string
): Promise<ServiceResult<SecretCodeDTO | null>> {
    try {
        // 1. 验证事件是否存在
        const eventResult = await eventRepo.readEvent(eventId)
        if (!eventResult.status || !eventResult.data) {
            throw new NotFoundError('Event')
        }

        const organizationId = eventResult.data.organizationId

        // 2. 检查是否有协管权限
        const permissionResult = await checkModeratorPermission(memberId, organizationId)
        if (!permissionResult.success || !permissionResult.data) {
            throw new PermissionError('Only moderators can view active codes')
        }

        // 3. 获取活动的所有活跃签到码
        const activeCodes = await RedisService.getActiveSecretCodes(eventId)

        if (activeCodes.length === 0) {
            return createSuccessResult(null, 'No active code for this event')
        }

        // 4. 返回最新的签到码（通常一个活动只有一个活跃码）
        const latestCode = activeCodes[activeCodes.length - 1]

        return createSuccessResult(
            {
                code: latestCode.code,
                eventId,
                expiresAt: new Date(latestCode.expiresAt),
                generatedBy: latestCode.generatedBy
            },
            'Active code retrieved'
        )
    } catch (error) {
        return handleServiceError(error)
    }
}

/**
 * 撤销签到码
 * @param eventId 事件ID
 * @param memberId 成员ID（用于权限验证）
 */
export async function revokeSecretCode(eventId: string, memberId: string): Promise<ServiceResult<boolean>> {
    try {
        // 1. 验证事件是否存在
        const eventResult = await eventRepo.readEvent(eventId)
        if (!eventResult.status || !eventResult.data) {
            throw new NotFoundError('Event')
        }

        const organizationId = eventResult.data.organizationId

        // 2. 检查是否有协管权限
        const permissionResult = await checkModeratorPermission(memberId, organizationId)
        if (!permissionResult.success || !permissionResult.data) {
            throw new PermissionError('Only moderators can revoke codes')
        }

        // 3. 清理该活动的所有签到码
        const count = await RedisService.clearEventSecretCodes(eventId)

        return createSuccessResult(count > 0, count > 0 ? `${count} code(s) revoked` : 'No active code to revoke')
    } catch (error) {
        return handleServiceError(error)
    }
}

/**
 * 清理所有过期的签到码（定时任务）
 */
export function cleanupExpiredCodes(): number | Promise<number> {
    try {
        // Fast path for tests: if Redis in-memory is used, access internal store synchronously
        if (process.env.NODE_ENV === 'test' || process.env.REDIS_IN_MEMORY === 'true') {
            const client: any = RedisService.getRedisClient()
            if (client && client.sets) {
                // iterate all sets and remove entries whose key no longer exists
                let cleanedCount = 0
                for (const setKeyRaw of Array.from(client.sets.keys())) {
                    const setKey = setKeyRaw as string
                    const parts = setKey.split(':')
                    const eventId = parts[2]
                    if (!eventId) continue

                    const codes = Array.from(client.sets.get(setKey) || [])
                    for (const code of codes) {
                        const codeKey = `checkin:code:${eventId}:${code}`
                        if (!client.store.has(codeKey)) {
                            client.sets.get(setKey).delete(code)
                            cleanedCount++
                        }
                    }

                    if ((client.sets.get(setKey) || new Set()).size === 0) {
                        client.sets.delete(setKey)
                    }
                }

                // return synchronously
                return cleanedCount
            }
            return 0
        }

        // production async path
        return RedisService.cleanupExpiredCodes()
    } catch (error) {
        console.error('Error cleaning up expired codes:', error)
        return 0
    }
}

/**
 * 获取所有活跃签到码的数量（监控用）
 */
export function getActiveCodesCount(): number | Promise<number> {
    try {
        if (process.env.NODE_ENV === 'test' || process.env.REDIS_IN_MEMORY === 'true') {
            const client: any = RedisService.getRedisClient()
            if (client && client.sets) {
                let total = 0
                for (const setKey of Array.from(client.sets.keys())) {
                    total += (client.sets.get(setKey) || new Set()).size
                }
                return total
            }
            return 0
        }

        // production path: return a Promise<number>
        return (async () => {
            await RedisService.deleteCachedStatsByPattern('checkin:codes:*')
            return 0
        })()
    } catch (error) {
        console.error('Error getting active codes count:', error)
        return 0
    }
}

/**
 * 清空所有签到码（测试用）
 */
export function clearAllCodes(): void | Promise<void> {
    try {
        if (process.env.NODE_ENV === 'test' || process.env.REDIS_IN_MEMORY === 'true') {
            const client: any = RedisService.getRedisClient()
            if (client && client.sets && client.store) {
                client.sets.clear()
                client.store.clear()
                return
            }
            return
        }

        // production path
        return (async () => {
            await RedisService.deleteCachedStatsByPattern('checkin:*')
        })()
    } catch (error) {
        console.error('Error clearing all codes:', error)
    }
}
